package settings

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/spf13/viper"
)

var appConf *AppConf

func GetAppConf() *AppConf { return appConf }

type AppConf struct {
	WorkDir           string
	*MonitorConf      `mapstructure:"monitor"`
	*DdnsConf         `mapstructure:"ddns"`
	*NotificationConf `mapstructure:"notification"`
	*LogConf          `mapstructure:"log"`
}

func InitSetting() {
	dir, _ := os.Executable()
	workDir := filepath.Dir(dir)
	appConf = &AppConf{
		WorkDir:          workDir,
		MonitorConf:      &MonitorConf{},
		DdnsConf:         &DdnsConf{},
		NotificationConf: &NotificationConf{},
		LogConf:          &LogConf{},
	}

	viper.SetConfigName("application")
	viper.SetConfigType("yml")
	viper.AddConfigPath(filepath.Join(workDir, "config/"))
	viper.ReadInConfig()
	viper.Unmarshal(appConf)

	appConf.Validate()
}

func (app *AppConf) Validate() {
	app.MonitorConf.validate()
	app.DdnsConf.validate()
	app.NotificationConf.validate()
	app.LogConf.validate()
}

// MonitorConf
type MonitorConf struct {
	Use      string `mapstructure:"use"`
	Para     string `mapstructure:"para"`
	Interval int    `mapstructure:"interval"`
}

func (mon *MonitorConf) validate() {
	if mon.Use == "" {
		err := fmt.Errorf("monitor's 'use': can not be empty")
		log.Fatal(err)
	}
	if mon.Para == "" {
		err := fmt.Errorf("monitor's 'para': can not be empty")
		log.Fatal(err)
	}
	if mon.Interval < 0 {
		err := fmt.Errorf("monitor's 'interval': can not be empty")
		log.Fatal(err)
	}
}

// DdnsConf
type DdnsConf struct {
	NoIpConf  *NoIpConf  `mapstructure:"noip"`
	Dynv6Conf *Dynv6Conf `mapstructure:"dynv6"`
}

func (ddns *DdnsConf) validate() {
	valid, err := ddns.NoIpConf.validate()
	if !valid {
		log.Fatal(err)
	}
	if err != nil {
		log.Print(err)
	}
	valid, err = ddns.Dynv6Conf.validate()
	if !valid {
		log.Fatal(err)
	}
	if err != nil {
		log.Print(err)
	}
}

type NoIpConf struct {
	Enable     bool   `mapstructure:"enable"`
	Ssl        bool   `mapstructure:"ssl"`
	User       string `mapstructure:"user"`
	Password   string `mapstructure:"password"`
	Api        string `mapstructure:"api"`
	MyHostname string `mapstructure:"myhostname"`
}

func (noip *NoIpConf) validate() (valid bool, err error) {
	valid = true
	if !noip.Enable {
		valid = true
		return
	}
	if noip.User == "" || noip.Password == "" {
		valid = false
		err = fmt.Errorf("ddns noip's 'user' or 'password': can't be empty")
		return
	}
	if noip.Api == "" {
		valid = false
		err = fmt.Errorf("ddns noip's 'api': can't be empty")
		return
	}
	return
}

type Dynv6Conf struct {
	Enable bool   `mapstructure:"enable"`
	Ssl    bool   `mapstructure:"ssl"`
	Token  string `mapstructure:"token"`
	Api    string `mapstructure:"api"`
	Zone   string `mapstructure:"zone"`
}

func (dynv6 *Dynv6Conf) validate() (valid bool, err error) {
	valid = true
	if !dynv6.Enable {
		valid = true
		return
	}
	if dynv6.Token == "" {
		valid = false
		err = fmt.Errorf("ddns dynv6's 'Token': can't be empty")
		return
	}
	if dynv6.Api == "" {
		valid = false
		err = fmt.Errorf("ddns dynv6's 'api': can't be empty")
		return
	}
	if dynv6.Zone == "" {
		valid = false
		err = fmt.Errorf("ddns dynv6's 'zone': can't be empty")
		return
	}
	return
}

// NotificationConf
type NotificationConf struct {
	Enable         bool            `mapstructure:"enable"`
	MailConf       *MailConf       `mapstructure:"mail"`
	ServerchanConf *ServerchanConf `mapstructure:"serverchan"`
}

func (noti *NotificationConf) validate() {
	if !noti.Enable {
		return
	}
	valid, err := noti.MailConf.validate()
	if !valid {
		log.Fatal(err)
	}
	valid, err = noti.ServerchanConf.validate()
	if !valid {
		log.Fatal(err)
	}
	if err != nil {
		log.Print(err)
	}
}

type MailConf struct {
	Enable   bool   `mapstructure:"enable"`
	Smtp     string `mapstructure:"smtp"`
	SmtpPort int    `mapstructure:"smtpport"`
	User     string `mapstructure:"user"`
	Password string `mapstructure:"password"`
	MailTo   string `mapstructure:"mailto"`
	Title    string `mapstructure:"title"`
}

func (mail *MailConf) validate() (valid bool, err error) {
	valid = true
	if !mail.Enable {
		valid = true
		return
	}
	if mail.Smtp == "" {
		valid = false
		err = fmt.Errorf("mail notification's 'smtp': can't be empty")
		return
	}
	if mail.SmtpPort == 0 {
		valid = false
		err = fmt.Errorf("mail notification's 'smtpport': can't be empty")
		return
	}
	if mail.User == "" || mail.Password == "" {
		valid = false
		err = fmt.Errorf("mail notification's 'user' or 'password': can't be empty")
		return
	}
	return
}

type ServerchanConf struct {
	Enable  bool   `mapstructure:"enable"`
	SendKey string `mapstructure:"sendkey"`
	Title   string `mapstructure:"title"`
	Channel string `mapstructure:"channel"`
}

func (serverchan *ServerchanConf) validate() (valid bool, err error) {
	valid = true
	if !serverchan.Enable {
		valid = true
		return
	}
	if serverchan.SendKey == "" {
		valid = false
		err = fmt.Errorf("serverchan notification's 'sendkey': can't by empty")
		return
	}
	if serverchan.Title == "" {
		valid = false
		err = fmt.Errorf("serverchan notification's 'title': can't by empty")
		return
	}
	if len(serverchan.Title) > 32 {
		valid = false
		err = fmt.Errorf("serverchan notification's 'title': too long, max 32")
		return
	}
	re := regexp.MustCompile(`^(\d+)\s*(\|\s*(\d+)){0,1}$`)
	m := re.FindStringSubmatch(serverchan.Channel)
	if m == nil {
		valid = false
		err = fmt.Errorf("serverchan notification's 'channel': max two channel")
		return
	}
	if m[2] != "" {
		serverchan.Channel = fmt.Sprintf("%s|%s", m[1], m[3])
	}
	return
}

type LogConf struct {
	Path  string `mapstructure:"path"`
	Level string `mapstructure:"level"`
}

func (logC *LogConf) validate() {
	dir, _ := os.Executable()
	workDir := filepath.Dir(dir)
	if logC.Path == "" {
		logC.Path = filepath.Join(workDir, "log/")
	}
	acceptedLevel := []string{"debug", "info", "error"}
	if logC.Level == "" {
		logC.Level = acceptedLevel[0]
		return
	}
	logC.Level = strings.ToLower(logC.Level)
	accepted := false
	for _, v := range acceptedLevel {
		if v == logC.Level {
			accepted = true
			break
		}
	}
	if !accepted {
		err := fmt.Errorf("log's 'level' not acceptable: can use %s", strings.Join(acceptedLevel, ","))
		log.Fatal(err)
	}
}
